/*
 * Copyright (C) 2007-2008  Vianney le Clément
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package fsahoraires.gui;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

import fsahoraires.Main;
import fsahoraires.programmes.Programme;
import fsahoraires.store.StoreManager;
import fsahoraires.store.StoreManager.LoadResult;

public class MainWindow {

	private final Shell shell;
	private final TreeManager tm;
	private final CTabFolder tabs;
	private final Label status;

	private Programme programme;

	private File file = null;
	private boolean legacy = false;
	private boolean modified = false;

	private Map<String, Image> images = new HashMap<String, Image>();

	public MainWindow(Display display) {
		shell = new Shell(display);
		shell.setImage(loadImage("horaire.png"));

		Menu menu = new Menu(shell, SWT.BAR);
		shell.setMenuBar(menu);

		final Tree tree = new Tree(shell, SWT.BORDER);
		final Sash sash = new Sash(shell, SWT.VERTICAL);
		tabs = new CTabFolder(shell, SWT.BORDER);
		tabs.setSimple(false);
		tabs.setSelectionBackground(display
				.getSystemColor(SWT.COLOR_LIST_SELECTION));
		tabs.setSelectionForeground(display
				.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
		status = new Label(shell, SWT.LEFT);

		tm = new TreeManager(this, tree, tabs);

		populateMenu(menu);

		// layout
		shell.setLayout(new FormLayout());
		FormData fd = new FormData();
		fd.top = new FormAttachment(0);
		fd.left = new FormAttachment(0);
		fd.right = new FormAttachment(sash);
		fd.bottom = new FormAttachment(status);
		tree.setLayoutData(fd);
		fd = new FormData();
		fd.top = new FormAttachment(0);
		fd.left = new FormAttachment(sash);
		fd.right = new FormAttachment(100);
		fd.bottom = new FormAttachment(status);
		tabs.setLayoutData(fd);
		fd = new FormData();
		fd.left = new FormAttachment(0);
		fd.right = new FormAttachment(100);
		fd.bottom = new FormAttachment(100);
		status.setLayoutData(fd);
		final FormData sashData = new FormData();
		final int limit = 20;
		sashData.left = new FormAttachment(30);
		sashData.top = new FormAttachment(0, 0);
		sashData.bottom = new FormAttachment(status);
		sash.setLayoutData(sashData);
		sash.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event e) {
				Rectangle sashRect = sash.getBounds();
				Rectangle shellRect = shell.getClientArea();
				int right = shellRect.width - sashRect.width - limit;
				e.x = Math.max(Math.min(e.x, right), limit);
				if (e.x != sashRect.x) {
					sashData.left = new FormAttachment(0, e.x);
					shell.layout();
				}
			}
		});

		// listeners
		shell.addListener(SWT.Close, new Listener() {
			public void handleEvent(Event event) {
				/*
				 * Toujours passer par la méthode exit()
				 */
				event.doit = false;
				exit();
			}
		});
		shell.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				if (programme != null)
					programme.close();
				for (Image img : images.values())
					img.dispose();
			}
		});

		// initialize
		init();
	}

	private void populateMenu(Menu menu) {
		MenuItem mi = new MenuItem(menu, SWT.CASCADE);
		mi.setText("&Fichier");
		Menu fichier = new Menu(shell, SWT.DROP_DOWN);
		mi.setMenu(fichier);

		mi = new MenuItem(fichier, SWT.PUSH);
		mi.setText("&Nouveau\tCtrl+N");
		mi.setAccelerator(SWT.CONTROL | 'N');
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				newFile();
			}
		});

		mi = new MenuItem(fichier, SWT.PUSH);
		mi.setText("&Ouvrir...\tCtrl+O");
		mi.setAccelerator(SWT.CONTROL | 'O');
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				openFile();
			}
		});

		mi = new MenuItem(fichier, SWT.SEPARATOR);

		mi = new MenuItem(fichier, SWT.PUSH);
		mi.setText("&Enregistrer\tCtrl+S");
		mi.setAccelerator(SWT.CONTROL | 'S');
		mi.setImage(loadImage("save.png"));
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				saveFile(false);
			}
		});

		mi = new MenuItem(fichier, SWT.PUSH);
		mi.setText("Enregistrer &sous...");
		mi.setImage(loadImage("saveas.png"));
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				saveFile(true);
			}
		});

		mi = new MenuItem(fichier, SWT.SEPARATOR);

		mi = new MenuItem(fichier, SWT.PUSH);
		mi.setText("&Quitter");
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				exit();
			}
		});

		mi = new MenuItem(menu, SWT.CASCADE);
		mi.setText("A&vancé");
		Menu advanced = new Menu(shell, SWT.DROP_DOWN);
		mi.setMenu(advanced);

		mi = new MenuItem(advanced, SWT.CHECK);
		mi.setText("&Afficher la cache");
		mi.setSelection(tm.isCacheShown());
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				tm.showCache(((MenuItem) e.widget).getSelection());
			}
		});

		mi = new MenuItem(advanced, SWT.PUSH);
		mi.setText("&Nettoyer la cache");
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				if (msgBox(
						"Ceci supprimera tous les cours en cache non utilisés,"
								+ " ainsi que les modifications que vous y avez apportées.\n"
								+ "\nVoulez-vous continuer?", SWT.ICON_QUESTION
								| SWT.YES | SWT.NO) == SWT.YES)
					programme.cleanCache();
			}
		});

		mi = new MenuItem(menu, SWT.CASCADE);
		mi.setText("&Aide");
		Menu aide = new Menu(shell, SWT.DROP_DOWN);
		mi.setMenu(aide);

		mi = new MenuItem(aide, SWT.PUSH);
		mi.setText("&À propos...");
		mi.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				msgBox("Horaires FSA v" + Main.VERSION + "\n\n"
						+ "Copyright (C) 2007-2008 Vianney le Clément\n"
						+ "Programme sous license GPL.\n"
						+ "Icônes de Mark James sous license CC Attribution.",
						SWT.ICON_INFORMATION);
			}
		});
	}

	public void openAndDispatch() {
		shell.open();
		Display display = shell.getDisplay();
		while (!shell.isDisposed())
			if (!display.readAndDispatch())
				display.sleep();
	}

	public void asyncExec(Runnable runnable) {
		Display display = shell.getDisplay();
		if (display.isDisposed())
			return;
		if (display.getThread().equals(Thread.currentThread()))
			runnable.run();
		else
			display.asyncExec(runnable);
	}

	/**
	 * Demande la fermeture du programme
	 */
	public void exit() {
		if (askSave())
			shell.dispose();
	}

	public Image loadImage(String name) {
		if (name == null)
			return null;
		Image img = images.get(name);
		if (img == null) {
			InputStream in = MainWindow.class.getResourceAsStream("res/"
					.concat(name));
			if (in != null) {
				img = new Image(shell.getDisplay(), in);
				try {
					in.close();
				} catch (IOException ex) {
				}
				images.put(name, img);
			}
		}
		return img;
	}

	/**
	 * Affiche une boîte de dialogue
	 * 
	 * @param text
	 * @param flags
	 * @return
	 */
	public int msgBox(String text, int flags) {
		return msgBox(shell, text, flags);
	}

	public static int msgBox(Shell shell, String text, int flags) {
		MessageBox box = new MessageBox(shell, flags);
		box.setText("Horaires FSA");
		box.setMessage(text);
		return box.open();
	}

	public File fileSaveDlg(String path, String filterName, String filterExt) {
		return fileSaveDlg(shell, path, filterName, filterExt);
	}

	public static File fileSaveDlg(Shell shell, String path, String filterName,
			String filterExt) {
		FileDialog dlg = new FileDialog(shell, SWT.SAVE);
		dlg.setFileName(path);
		if (filterName != null && filterExt != null) {
			dlg
					.setFilterNames(new String[] { filterName,
							"Tous les fichiers" });
			dlg
					.setFilterExtensions(new String[] { "*.".concat(filterExt),
							"*" });
		}
		while (true) {
			String p = dlg.open();
			if (p == null)
				return null;
			File f = new File(p);
			if (filterExt != null) {
				if (!f.getName().contains("."))
					f = new File(p + "." + filterExt);
			}
			if (f.exists()) {
				if (msgBox(shell, "'" + p
						+ "': ce fichier existe déjà.\nVoulez-vous l'écraser?",
						SWT.ICON_INFORMATION | SWT.YES | SWT.NO) == SWT.YES)
					return f;
			} else {
				return f;
			}
		}
	}

	public Shell getShell() {
		return shell;
	}

	public Programme getProgramme() {
		return programme;
	}

	public TreeManager getTreeManager() {
		return tm;
	}

	/**
	 * Définit le texte dans la barre d'état. Cette méthode est thread-safe.
	 * 
	 * @param text
	 */
	public void setStatus(final String text) {
		asyncExec(new Runnable() {
			public void run() {
				if (status.isDisposed())
					return;
				status.setText(text);
			}
		});
	}

	/**
	 * Met le bit de modification à true
	 */
	public void touchModified() {
		modified = true;
		updateTitle();
	}

	private void init() {
		file = StoreManager.getLastOpened();
		LoadResult result;
		if (file == null || (result = StoreManager.load(file)) == null) {
			legacy = false;
			programme = new Programme();
		} else {
			legacy = result.isLegacy();
			programme = result.getProgramme();
		}
		modified = false;
		updateTitle();
		tm.rebuildAll();
	}

	public void newFile() {
		if (!askSave())
			return;

		file = null;
		legacy = false;
		if (programme != null)
			programme.close();
		programme = new Programme();
		modified = false;
		updateTitle();
		tm.rebuildAll();
	}

	public void openFile() {
		if (!askSave())
			return;
		FileDialog dlg = new FileDialog(shell, SWT.OPEN);
		dlg
				.setFilterNames(new String[] { "Documents XML",
						"Tous les fichiers" });
		dlg.setFilterExtensions(new String[] { "*.xml", "*" });
		String path = dlg.open();
		if (path == null)
			return;
		File f = new File(path);
		LoadResult result = StoreManager.load(f);
		if (result == null) {
			msgBox("Erreur lors de l'ouverture du fichier.\n"
					+ "La console peut fournir plus de détails.",
					SWT.ICON_ERROR);
			return;
		}
		programme.close();
		programme = result.getProgramme();
		legacy = result.isLegacy();
		file = f;
		modified = false;
		updateTitle();
		tm.rebuildAll();
	}

	/**
	 * Demander s'il faut sauver si le fichier a été modifié.
	 * 
	 * @return true si on peut poursuivre, false si l'utilisateur a appuyé sur
	 *         annuler.
	 */
	private boolean askSave() {
		if (modified) {
			switch (msgBox("Le programme actuel a été modifié. "
					+ "Voulez-vous enregistrer les modifications?",
					SWT.ICON_INFORMATION | SWT.YES | SWT.NO | SWT.CANCEL)) {
			case SWT.CANCEL:
				return false;
			case SWT.YES:
				return saveFile(false);
			}
		}
		return true;
	}

	public boolean saveFile(boolean askFile) {
		File f = file;
		if (askFile || f == null) {
			f = fileSaveDlg(f == null ? null : f.getPath(), "Documents XML",
					"xml");
			if (f == null)
				return false;
		} else if (legacy) {
			if (msgBox(
					"Le fichier que vous avez ouvert est écrit dans un ancien format. "
							+ "Si vous l'enregistrez maintenant, vous ne pourrez plus le lire "
							+ "dans les versions antérieurs. Voulez-vous continuer?",
					SWT.ICON_INFORMATION | SWT.YES | SWT.NO) == SWT.NO)
				return false;
		}
		if (!StoreManager.save(programme, f)) {
			msgBox("Une erreur s'est produite lors de l'enregistrement.\n"
					+ "La console peut fournir plus de détails.",
					SWT.ICON_ERROR);
			return false;
		}
		file = f;
		modified = false;
		updateTitle();
		return true;
	}

	private void updateTitle() {
		StringBuilder str = new StringBuilder("Horaires FSA");
		if (file != null)
			str.append(" - ").append(file.getName());
		if (modified)
			str.append('*');
		shell.setText(str.toString());
	}

}
